package com.lin.generate.config.datasource;

import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.sql.DataSource;

import com.alibaba.druid.wall.WallConfig;
import com.lin.generate.common.utill.SessionUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.transaction.PlatformTransactionManager;

import com.alibaba.druid.pool.DruidDataSource;
import com.github.pagehelper.PageHelper;
/**
 * @description TODO
 * @Author: z156940l@outlook.com
 * @className DataSourceConfig
 * @Date: 2023/6/1 23:30
 */
@Configuration
@MapperScan(basePackages = "com.lin.generate.mapper", sqlSessionFactoryRef = "sqlSessionFactory")
//@EnableConfigurationProperties(DataSourceBean.class)
public class DataSourceConfig {

    protected Log log = LogFactory.getLog(DataSourceConfig.class);
    private static DynamicDataSource dynamicDataSource = new DynamicDataSource();
    @Autowired
    private DataSourceBean dataSourceBean;

    /**
     * 主数据库
     * @return
     * @throws Exception
     */
    public DataSource masterDataSource() throws Exception {
        if(dataSourceBean == null) {
            throw new NullPointerException("创建主数据源时[DataSourceBean]为空");
        }
        return dynamicDataSource.createDataSource(dataSourceBean);
    }

    /**
     * 切换到对应的数据源
     * @param db 构建DataSource所需properties
     * @throws Exception
     */
    public static void chageDB(DataSourceBean db) throws Exception {
        if(db.getDataSourceName() == null) {
            throw new NullPointerException("切换数据源时dataSourceName为空");
        }
        dynamicDataSource.selectDataSource(db.getDataSourceName(), db);
    }

    /**
     * 动态数据源
     *
     * @return
     * @throws Exception
     */
    @Bean(name="dataSource")
    public DataSource dataSource() throws Exception {
        DataSource dataSource = masterDataSource();
        // 默认数据源
        dynamicDataSource.setDefaultTargetDataSource(dataSource);
        DynamicDataSource.setDefaultDataSource(dataSource);
        DynamicDataSourceContextHolder.setDB(DynamicDataSourceContextHolder.DEFAULT_DS);
        return dynamicDataSource;
    }


    // 提供SqlSeesion
    @Bean(name = "sqlSessionFactory")
    @Primary
    public SqlSessionFactory sqlSessionFactoryBean() throws Exception {

        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource());
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        Interceptor[] plugins = new Interceptor[] { pageHelper() }; // 重新配置了sessionFactory后
        // 需要重新陪着pagehelper
        sqlSessionFactoryBean.setPlugins(plugins);
        // 查看一下怎么替换
//        sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("mybatis-config.xml"));
        sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath:/mapper/*/*.xml"));
        return sqlSessionFactoryBean.getObject();
    }

    /**
     *
     * @Title pageHelper
     * @Description 配置mybatis的分页插件
     * @return PageHelper 返回类型
     */
    @Bean(name = "PageHelper")
    public PageHelper pageHelper() {
        log.info("MyBatisPageHelperConfiguration.pageHelper()");
        PageHelper pageHelper = new PageHelper();
        Properties p = new Properties();
        p.setProperty("offsetAsPageNum", "true");
        p.setProperty("rowBoundsWithCount", "true");
        p.setProperty("reasonable", "true");
        pageHelper.setProperties(p);
        return pageHelper;
    }

    @Bean(name = "transactionManager")
    @Primary
    public PlatformTransactionManager transactionManager() throws Exception {
        return new DataSourceTransactionManager(dataSource());
    }
}

class DynamicDataSource extends AbstractRoutingDataSource {
    private static final Log log = LogFactory.getLog(DynamicDataSource.class);

    private static DataSource defaultDataSource;

    private static final Map<String,DruidDataSource> targetDataSource = new HashMap<>();


    @Override
    protected DataSource determineTargetDataSource() {
        // 根据数据库选择方案，拿到要访问的数据库
        String dataSourceName = (String) determineCurrentLookupKey();
        if(DynamicDataSourceContextHolder.DEFAULT_DS.equals(dataSourceName)) {
            // 访问默认主库
            return defaultDataSource;
        }

        // 根据数据库名字，从已创建的数据库中获取要访问的数据库
        DataSource dataSource = (DataSource) targetDataSource.get(dataSourceName);
        if(null == dataSource) {
            // 从已创建的数据库中获取要访问的数据库，如果没有则创建一个
            try {
                dataSource = this.selectDataSource(dataSourceName, SessionUtil.getDataSourceBean());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return dataSource;
    }



    @Override
    protected Object determineCurrentLookupKey() {
        String dataSourceName = DynamicDataSourceContextHolder.getDB();
        if(dataSourceName == null) {
            dataSourceName = DynamicDataSourceContextHolder.DEFAULT_DS;
        }
        log.debug("数据源为{" + dataSourceName + "}");
        return dataSourceName;
    }

    public void addTargetDataSource(String key, DruidDataSource dataSource) {
        DynamicDataSource.targetDataSource.put(key, dataSource);
    }


    /**
     * 该方法为同步方法，防止并发创建两个相同的数据库 使用双检锁的方式，防止并发
     *
     * @param dbType
     * @return
     * @throws Exception
     */
    public synchronized DataSource selectDataSource(String dbType,DataSourceBean db) throws Exception {
        // 再次从数据库中获取，双检锁
        DataSource obj = (DataSource) DynamicDataSource.targetDataSource.get(dbType);
        if (null != obj) {
            DynamicDataSourceContextHolder.setDB(dbType);
            return obj;
        }
        // 为空则创建数据库
        DruidDataSource dataSource = this.createDataSource(db);
        if (null != dataSource) {
            // 将新创建的数据库保存到map中
            this.setDataSource(dbType, dataSource);
            return dataSource;
        } else {
            throw new Exception("创建数据源失败！");
        }
    }

    public void setDataSource(String type, DruidDataSource dataSource) {
        this.addTargetDataSource(type, dataSource);
        DynamicDataSourceContextHolder.setDB(type);
    }

    //创建数据源
    public DruidDataSource createDataSource(DataSourceBean db) throws SQLException {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(db.getUrl());
        dataSource.setUsername(db.getUserName());
        dataSource.setPassword(db.getPassWord());
        dataSource.setDriverClassName(db.getDriverClassName());
        dataSource.setInitialSize(db.getInitialSize());
        dataSource.setMinIdle(db.getMinIdle());
        dataSource.setMaxActive(db.getMaxActive());
        dataSource.setMaxWait(db.getMaxWait());
        dataSource.setTimeBetweenEvictionRunsMillis(db.getTimeBetweenEvictionRunsMillis());
        dataSource.setMinEvictableIdleTimeMillis(db.getMinEvictableIdleTimeMillis());
        dataSource.setValidationQuery(db.getValidationQuery());
        dataSource.setTestWhileIdle(db.getTestWhileIdle());
        dataSource.setTestOnBorrow(db.getTestOnBorrow());
        dataSource.setTestOnReturn(db.getTestOnReturn());
        dataSource.setPoolPreparedStatements(db.getPoolPreparedStatements());
        dataSource.setMaxPoolPreparedStatementPerConnectionSize(db.getMaxPoolPreparedStatementPerConnectionSize());
        dataSource.setFilters(db.getFilters());
        dataSource.setConnectionProperties(db.getConnectionProperties());
        dataSource.setUseGlobalDataSourceStat(true);

        return dataSource;
    }

    /**
     * 该方法重写为空，因为AbstractRoutingDataSource类中会通过此方法将，targetDataSources变量中保存的数据源交给resolvedDefaultDataSource变量
     * 在本方案中动态创建的数据源保存在了本类的targetDataSource变量中。如果不重写该方法为空，会因为targetDataSources变量为空报错
     * 如果仍然想要使用AbstractRoutingDataSource类中的变量保存数据源，则需要在每次数据源变更时，调用此方法来为resolvedDefaultDataSource变量更新
     */
    @Override
    public void afterPropertiesSet() { }

    public static DataSource getDefaultDataSource() {
        return defaultDataSource;
    }

    public static void setDefaultDataSource(DataSource defaultDataSource) {
        DynamicDataSource.defaultDataSource = defaultDataSource;
    }

    @Bean(name = "wallConfig")
    public WallConfig wallConfig() {
        WallConfig wallConfig = new WallConfig();
        wallConfig.setMultiStatementAllow(true);//允许一次执行多条语句
        wallConfig.setNoneBaseStatementAllow(true);//允许一次执行多条语句
        return wallConfig;

    }
}
